﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

using WebServer;
using WebServer.HTDOCS;
using WebServer.Modules;

namespace WebServerLite
{
	class Program
	{
		private static int _ErrorCount = 0;
		private static FileInfo _ConfigFile = IOUtility.EnsureFile(Core.BaseDirectory + "\\" + Config.SystemName + ".cfg");

		static void Main(string[] args)
		{
			Events.CoreClosed += new WebServer.CoreClosedEventHandler(Events_CoreClosed);

			InitializeConfig();

			try
			{
				Core.Start();
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.ToString());

				Console.WriteLine("The application encountered a fatal error and must now close.");
				Console.WriteLine("Press any key to close the application.");

				while (Console.ReadKey() == null)
				{ }
			}
			finally
			{
				Events.InvokeCoreClosed();
			}
		}

		private static void Events_CoreClosed()
		{
			GenerateConfig();
		}

		private static void GenerateConfig()
		{
			_ConfigFile = IOUtility.EnsureFile(Core.BaseDirectory + "\\" + Config.SystemName + ".cfg", true);

			using (StreamWriter writer = new StreamWriter(_ConfigFile.Open(FileMode.Truncate, FileAccess.Write)))
			{
				writer.WriteLine("##	Main {0} configuration options file, all options must be affixed with ';'", Config.SystemName);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	Enables or Disables the {0}", Config.SystemName);
				writer.WriteLine("##	Valid parameters: '1' or '0', 'yes' or 'no', 'on' or 'off', 'true' or 'false'");
				writer.WriteLine("ENABLED={0};", (bool)Config.Enabled);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	Enables or Disables Stand-Alone mode.");
				writer.WriteLine("##	This effects how the {0} behaves when encountering fatal exceptions", Config.SystemName);
				writer.WriteLine("##	Valid parameters: '1' or '0', 'yes' or 'no', 'on' or 'off', 'true' or 'false'");
				writer.WriteLine("STAND_ALONE={0};", (bool)Config.StandAloneMode);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	Enables or Disables Console output from the {0}", Config.SystemName);
				writer.WriteLine("##	Valid parameters: '1' or '0', 'yes' or 'no', 'on' or 'off', 'true' or 'false'");
				writer.WriteLine("CONSOLE_MESSAGES={0};", (bool)Config.ConsoleMessages);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	Enables or Disables Error Logging in the {0}", Config.SystemName);
				writer.WriteLine("##	If Logging is disabled (see: LOG_FILE_PATH),");
				writer.WriteLine("##	error logging will also be disabled regardless of this option value", Config.SystemName);
				writer.WriteLine("##	Valid parameters: '1' or '0', 'yes' or 'no', 'on' or 'off', 'true' or 'false'");
				writer.WriteLine("ERROR_LOGGING={0};", (bool)Config.ErrorLogging);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	The maximum total simultanious Client Connections allowed to connect to the {0} at any one time", Config.SystemName);
				writer.WriteLine("##	Valid parameters: '0' to '1000', '0' is equal to 'unlimited'");
				writer.WriteLine("MAX_CONNECTIONS={0};", (int)Config.MaxConnections);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	The delay between cleanup operations on 'AFK' Client connections and other cahced variables");
				writer.WriteLine("##	Valid parameters: '10' to '300', this value is measured in 'seconds'");
				writer.WriteLine("CLEANUP_INTERVAL={0};", (int)Config.CleanupInterval.TotalSeconds);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	The amount of time Client connections are stored in the cache before they are marked as 'AFK'");
				writer.WriteLine("##	Valid parameters: '1' to '3600', this value is measured in 'seconds'");
				writer.WriteLine("AFK_LIMIT={0};", (int)Config.AFKLimit.TotalSeconds);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	The amount of time between requests that must pass by before the request is marked as 'spam'");
				writer.WriteLine("##	Valid parameters: '1' to '10', this value is measured in 'seconds'");
				writer.WriteLine("FLOOD_THRESHOLD={0};", (int)Config.ClientFloodThreshold.TotalSeconds);

				writer.WriteLine(String.Empty);

				writer.WriteLine("##	The Default URL, used for constructing the WebObjects Registry");
				writer.WriteLine("##	Valid parameters: 'http://www.YourDomain.com', URL must start with 'http://' and be well-formed, or an exception will be thrown");
				writer.WriteLine("DEFAULT_URL={0};", (string)Config.DefaultURL);

				writer.Close();
			}
		}

		private static void InitializeConfig()
		{
			if (!_ConfigFile.Exists)
			{
				GenerateConfig();
			}
			else
			{
				_ConfigFile = IOUtility.EnsureFile(Core.BaseDirectory + "\\" + Config.SystemName + ".cfg", false);

				string body = String.Empty;

				string[] lines = File.ReadAllLines(_ConfigFile.FullName);

				foreach (string line in lines)
				{
					if (String.IsNullOrEmpty(line))
						continue;

					string ln = line.Trim();

					if (ln.StartsWith("#"))
						continue;

					body += ln;
				}

				string[] options = body.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

				foreach (string option in options)
				{
					if (String.IsNullOrEmpty(option))
						continue;

					string opt = option.Trim();

					string[] kvp = opt.Split('=');

					if (String.IsNullOrEmpty(kvp[0]) || String.IsNullOrEmpty(kvp[1]))
						continue;

					string key = kvp[0], val = kvp[1];

					SetOption(key, val);
				}
			}

			if (_ErrorCount > 0)
			{
				Core.ConsoleWriteLine("{0} error{1} encountered while loading the configuration file...", _ErrorCount, _ErrorCount > 1 ? "s were" : " was");
				Core.ConsoleWriteLine("Any configuration options that may not have been set were automatically set to their default values.");
				Core.ConsoleWriteLine("Your configuration file will be changed to match your options and the current defaults of any options that were not set.");
				Core.ConsoleWriteLine("Press any key to repair the configuration file and continue...");

				while (Core.ConsoleReadKey() == null)
				{ }

				GenerateConfig();
			}
		}

		private static void SetOption(string key, string val)
		{
			if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(val))
				return;

			key = key.ToUpper();

			switch (key)
			{
				default: { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, "Invalid option specified"); _ErrorCount++; } break;
				case "ENABLED":
					{
						try { Config.Enabled = ParseBool(val); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "STAND_ALONE":
					{
						try { Config.StandAloneMode = ParseBool(val); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "CONSOLE_MESSAGES":
					{
						try { Config.ConsoleMessages = ParseBool(val); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "ERROR_LOGGING":
					{
						try { Config.ErrorLogging = ParseBool(val); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "MAX_CONNECTIONS":
					{
						try { Config.MaxConnections = ParseInt(val, 0, 1000); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "CLEANUP_INTERVAL":
					{
						try { Config.CleanupInterval = ParseTimeSpan(val, 30, 300); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "AFK_LIMIT":
					{
						try { Config.AFKLimit = ParseTimeSpan(val, 1, 3600); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "FLOOD_THRESHOLD":
					{
						try { Config.ClientFloodThreshold = ParseTimeSpan(val, 1, 10); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;

				case "DEFAULT_URL":
					{
						try { Config.DefaultURL = ParseUrl(val); }
						catch (Exception ex) { Core.ConsoleWriteError("Parse Error: '{0}' - {1}", key, ex.Message); _ErrorCount++; }
					} break;
			}
		}

		private static bool ParseBool(string val)
		{
			val = val.ToUpper();

			if (val == "YES" || val == "1" || val == "ON" || val == "TRUE")
				return true;
			else if (val == "NO" || val == "0" || val == "OFF" || val == "FALSE")
				return false;
			else
				throw new Exception(String.Format("Could not successfully parse a boolean value from '{0}'", val));
		}

		private static int ParseInt(string val, int min, int max)
		{
			int number = Int32.Parse(val);

			if (number < min)
				number = min;

			if (number > max)
				number = max;

			return number;
		}

		private static TimeSpan ParseTimeSpan(string val, int min, int max)
		{
			int totalSeconds = ParseInt(val, min, max);

			return TimeSpan.FromSeconds(totalSeconds);
		}

		private static string ParseFilePath(string val)
		{
			val = val.Trim();

			string pat = @"[^/\\]{1}(([^\\/]))+\\/*";

			Regex reg = new Regex(pat);

			if (reg.IsMatch(val))
				return val;
			else
				throw new Exception(String.Format("Could not successfully parse a file path value from '{0}'", val));
		}

		private static string ParseUrl(string val)
		{
			val = val.Trim();

			string pat = @"((https?):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)";

			Regex reg = new Regex(pat);

			if (reg.IsMatch(val))
				return val;
			else
				throw new Exception(String.Format("Could not successfully parse a URL value from '{0}'", val));
		}
	}
}
